from ui import ui
from PyQt5.QtCore import QThread
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QFileDialog
import message as msg
import protocol
import pack as pk
from protocol import Protocol
import config as cfg
import os
import time
import math

PACK_SIZE = 512

UPDATE_STA_IDLE = 0
UPDATE_STA_WAIT = 1
UPDATE_STA_UPLOAD = 2
UPDATE_STA_END = 3

UPDATE_EVENT = 0xa9
UPDATE_OP_REQ = 0
UPDATE_OP_DATA = 1
UPDATE_OP_END = 2

MODULE_MAX_SIZE = 128 * 1024


class MasterContorlUpdate(QThread):
    def __init__(self):
        super().__init__()
        self.is_exit = False
        self.priority = Protocol()
        self.init_env()
        self.init_used_time_show()
        ui.update_button.clicked.connect(self.event_update)
        self.start()

    def init_env(self):
        self.app_path = ''
        self.app_pack = []
        self.update_status = UPDATE_STA_IDLE
        self.err_count = 0
        self.start_time = time.time()
        ui.progress_bar_signal.emit(0)
        ui.used_time_signal.emit(0)

    def init_used_time_show(self):
        self.timer_used = QTimer()
        self.timer_used.timeout.connect(self.show_used_time)
        self.timer_used.start()

    def show_used_time(self):
        if self.update_status != UPDATE_STA_IDLE:
            used_time = int(time.time() - self.start_time)
            ui.used_time_signal.emit(used_time)
        self.timer_used.start(1000)

    def wait_mc_data(self, timeout_ms):
        end_time = time.time() + (timeout_ms / 1000)
        while end_time > time.time():
            ch = msg.recv(1)
            if ch == None:
                self._failed_update()
                break
            if self.priority.unpack(ch) == protocol.RET_SUCCESS:
                return True
        return False

    def get_mc_data(self):
        ''' 需要在解包成功后调用
            返回: 去掉包头的数据
        '''
        data = self.priority.get_data()
        return data

    def get_file_path(self):
        path = os.path.dirname(cfg.get("APP_PATH", "./"))
        if not os.path.exists(path):
            path = ''
        app_path = QFileDialog.getOpenFileName(ui, "Open upgrade file", path)
        app_path = app_path[0]
        if app_path != '':
            self.app_path = app_path
            cfg.set("APP_PATH", self.app_path)
            file_size = os.path.getsize(app_path)
            print("size:{:.2f}k".format(file_size / 1024))
            ui.size_lable.setText("size:{:.2f}k".format(file_size / 1024))
        return self.app_path != ''

    def pack(self, data):
        if pk.is_need_pack(data):
            if len(data) < MODULE_MAX_SIZE:
                print("pack module")
                return pk.easy_pack(pk.MINOR_IMAGE_TYPE_MODULE, data)
            else:
                print("pack controller")
                return pk.easy_pack(pk.MINOR_IMAGE_TYPE_CONTROLLER, data)
        else:
            return data

    def load_updata_file(self):
        with open(self.app_path, "rb") as f:
            data = f.read()
            data = bytearray(data)
            data = self.pack(data)
            pack_count = math.ceil(len(data) / PACK_SIZE)
            loop = len(data) // PACK_SIZE
            ui.packet_lable.setText("pack:0/{}".format(pack_count))
            for i in range(loop):
                pack = bytearray([(i >> 8) & 0xff, i & 0xff])
                pack += data[i * PACK_SIZE : i * PACK_SIZE + PACK_SIZE]
                self.app_pack.append(pack)
            if loop != pack_count:
                pack = bytearray([(loop >> 8) & 0xff, loop & 0xff])
                pack += data[loop * PACK_SIZE : len(data)]
                self.app_pack.append(pack)
            self.pack_count = len(self.app_pack)

    def event_update(self):
        self.init_env()
        if self.get_file_path():
            self.load_updata_file()
            self.update_status = UPDATE_STA_WAIT

    def _failed_update(self):
        ui.button_info_signal.emit("Upgrade failed")
        ui.update_button.setStyleSheet("background-color: red")
        self.init_env()

    def _wait_update(self):
        if not msg.isOpen():
            ui.button_info_signal.emit("Wait for the serial")
            ui.update_button.setStyleSheet("background-color: yellow")
            time.sleep(1)
            return
        if self.app_pack[0][0] == pk.MINOR_IMAGE_TYPE_CONTROLLER:
            msg.send(b"M2000 F100\r\n")
            msg.send(b"M1999\r\n")
        timeout = time.time() + 10
        while timeout > time.time():
            ui.button_info_signal.emit("Wait for program erase")
            cmd = Protocol.pack(UPDATE_EVENT, UPDATE_OP_REQ)
            msg.send(cmd)
            print(cmd)
            ui.update_button.setStyleSheet("background-color: white")
            if self.wait_mc_data(2000):
                data = self.get_mc_data()
                print(data)
                if data[0] == UPDATE_EVENT + 1 and data[1] == UPDATE_OP_REQ:
                    self.update_status = UPDATE_STA_UPLOAD
                    ui.button_info_signal.emit("upgrading")
                    return
        self._failed_update()

    def _send_pack(self, pack_index):
        if pack_index < self.pack_count:
            cmd = Protocol.pack(UPDATE_EVENT, UPDATE_OP_DATA, self.app_pack[pack_index])
            msg.send(cmd)
        else:
            msg.send(Protocol.pack(UPDATE_EVENT, UPDATE_OP_END))
            self.update_status = UPDATE_STA_END
        ui.progress_bar_signal.emit(pack_index / self.pack_count * 100)
        ui.packet_lable.setText("pack:{}/{}".format(pack_index, self.pack_count))

    def _upload_file(self):
        if self.wait_mc_data(4000):
            data = self.get_mc_data()
            print(data)
            if data[0] == UPDATE_EVENT + 1 and data[1] == UPDATE_OP_DATA:
                self.pack_index = (data[2] << 8) + data[3]
                self._send_pack(self.pack_index)
        else:
            if self.update_status == UPDATE_STA_IDLE:
                return
            self.err_count += 1
            if self.err_count > 5:
                self._failed_update()
                return
            self._send_pack(self.pack_index)

    def _end_update(self):
        count = 0
        while (count < 5):
            if self.wait_mc_data(1000):
                data = self.get_mc_data()
                print(data)
                if data[0] == UPDATE_EVENT + 1 and data[1] == UPDATE_OP_END:
                    ui.button_info_signal.emit("update successfully")
                    ui.update_button.setStyleSheet("background-color: green")
                    break
            count += 1
        if count == 5:
            self._failed_update()
        self.update_status = UPDATE_STA_IDLE

    def run(self):
        while not self.is_exit:
            if self.update_status == UPDATE_STA_IDLE:
                time.sleep(0.1)
                continue
            if self.update_status == UPDATE_STA_WAIT:
                self._wait_update()
            elif self.update_status == UPDATE_STA_UPLOAD:
                self._upload_file()
            else:
                self._end_update()


mc_update = MasterContorlUpdate()


    
